#include <iostream>
#include <string.h>
using namespace std;

/*
  01 结构变量(struct)
  02 动态存储分配的基本概念(内存分配函数,空指针)
  03 动态分配字符串(malloc函数说明)
  04 释放存储空间(垃圾内存 || free函数 || 悬空指针)

*/

void fun01();
void fun03();
char *fun03_concat(const char *s1,const char *s2);

int main(){

  fun01();
  fun03();

}

//================================= 01 结构变量 =====================================
void fun01(){
  /*
    结构的元素（成员）可能具有不同的类型，结构成员都有名字。
    在其他一些语言中，经常把结构称为记录（record）​，把结构的成员称为字段（f​ield）​。
  */

  //结构在内存中存储的形式 {int，char[10],age}  18个字节
  struct {
    int no;
    char name[10];
    int age;
  } 
  zhang3 = {1,"张三",20}
  ,lisi = {.no = 2, .name = "李四", .age = 20}; // 可以对某些字段不初始化,增加可阅读性

  //结构成员是通过名字而不是通过位置访问的
  cout << "001 - 结构体 zhang3 的名字 : " << zhang3.name << endl;
  
  //数组不能用=运算符进行复制，所以结构可以用=运算符复制
  //wangwu = lisi;

  //不能使用运算符==和!=来判定两个结构相等还是不等。


  //结构类型: 结构标记的声明
  struct Student{
    int stu_no;
    char stu_name[10];
    int stu_age;
  }; //右花括号后的分号是必不可少的，它表示声明结束。

  Student stu1 = {1,"张园园",22};
  Student stu2 = {2,"杨永",21};

  //用typedef来定义真实的类型名
  typedef struct{
    int tea_no;
    char tea_name[10];
    int tea_age;
    struct Student stu[10]; //嵌套的结构数组
  } Teacher; 

  Teacher tea1 =  {1,"李老师",28,{stu1,stu2}};

  cout << "002 - 结构体 老师 tea1 学生的名字 : " << tea1.stu[1].stu_name << endl;

  //结构用于链表（➤17.5节）时，强制使用声明结构标记。
  //一般使用结构标记的声明

  //结构体作为参数
  //为了避免这类系统开销，有时用传递指向结构的指针来代替传递结构本身是很明智的做法。类似地，可以使函数返回指向结构的指针来代替返回实际的结构

  enum state {
    STOP = 10,RUN = 0
  };

  int run_state = STOP;
  cout << "003 - 枚举 运行状态: " << run_state << endl;


}


//================================= 02 动态存储分配的基本概念 =====================================
void fun02(){
  /*
      无论如何增大数组，始终有可能填满数组。
      幸运的是，还有别的办法。C语言支持动态存储分配，即在程序执行期间分配内存单元的能力。
      利用动态存储分配，可以设计出能根据需要扩大（和缩小）的数据结构。


      == 内存分配函数
      为了动态地分配存储空间，需要调用三种内存分配函数的一种，这些函数都是声明在<stdlib.h>头中的。

      ·malloc函数——分配内存块，但是不对内存块进行初始化。
      ·calloc函数——分配内存块，并且对内存块进行清零。
      ·realloc函数——调整先前分配的内存块大小。

      malloc函数是最常用的一种。因为malloc函数不需要对分配的内存块进行清零，所以它比calloc函数更高效。

      函数会返回void *类型的值。void *类型的值是“通用”指针，本质上它只是内存地址。


      == 空指针
      当调用内存分配函数时，总存在这样的可能性：找不到满足我们需要的足够大的内存块。如果真的发生了这类问题，函数会返回空指针（null pointer）​。
      空指针是“不指向任何地方的指针”​
      
      空指针用名为NULL的宏来表示，所以可以用下列方式测试malloc函数的返回值
      p = malloc(1000); //allocation 分配

      if(p == NULL){
        //对分配失败时的操作
      }

      所有非空指针都为真，而只有空指针为假。
      if(!p){
        //对分配失败时的操作
      }

      
  */
}


//================================= 03 动态分配字符串 =====================================
void fun03(){
  /*
    //malloc函数原型
    void *malloc(size_t _Size);

    malloc函数分配size个字节的内存块，并且返回指向该内存块的指针。
    注意，size的类型是size_t​，这是在C语言库中定义的无符号整数类型


    为给n个字符的字符串分配内存空间，可以写成

    p = malloc(n + 1); //赋值操作时会把malloc函数返回的通用指针转化为char *类型

    这里的p是char *类型变量,在执行赋值操作时会把malloc函数返回的通用指针转化为char *类型，而不需要强制类型转换。​


    由于使用malloc函数分配的内存不需要清零或者以任何方式进行初始化，所以p指向带有n+1个字符的未初始化的数组

    对上述数组进行初始化的一种方法是调用strcpy函数
    strcpy(p,"abc"); //数组中的前4个字符分别为a、b、c和\0：

  */

  const char *str01 = "abc";
  const char *str02 = "123";
  char *concat_str = fun03_concat(str01,str02);

  cout << "004 - 拼接字符串: " << concat_str << endl;
}

//连接两个字符串  把两个字符串连接起来而不改变其中任何一个字符串 || strcat函数改变了作为参数传递过来的一个字符串
char *fun03_concat(const char *s1,const char *s2){
  char *result;
  result = (char *)malloc(strlen(s1) + strlen(s2) + 1);

  if(result == NULL){
     cout << "分配内存失败" << endl;
     exit(EXIT_FAILURE);
  }

  strcpy(result,s1);
  strcat(result,s2); 
  //char * strcat(char * s1, const char * s2); strcat函数把字符串s2的内容追加到字符串s1的末尾，并且返回字符串s1（指向结果字符串的指针)
  
  return result;
}



//================================= 04 释放存储空间(垃圾内存 || free函数 || 悬空指针) =====================================
void fun04(){
  /*
    malloc函数和其他内存分配函数所获得的内存块都来自一个称为堆（heap）的存储池。

    垃圾内存
    程序可能分配了内存块，然后又丢失了对这些块的记录，因而浪费了空间。

    如:
    p = malloc(...);
    s = malloc(...);
    p = s;

    这样一来,没有指针指向p之前的内存块,导致它不可访问

    对程序而言，不可再访问到的内存块被称为是垃圾（garbage）​。留有垃圾的程序存在内存泄漏（memroy leak）现象。
    一些语言提供垃圾收集器（garbage collector）用于垃圾的自动定位和回收，但是C语言不提供。
    
    相反，每个C程序负责回收各自的垃圾，方法是调用free函数来释放不需要的内存。


    free函数
    函数原型: void free(void *ptr);
    free(p);

    调用free函数会释放p所指向的内存块。然后此内存块可以被后续的malloc函数或其他内存分配函数的调用重新使用。


    悬空指针
    虽然free函数允许收回不再需要的内存，但是使用此函数会导致一个新的问题：悬空指针（dangling pointer）​。
    调用free(p)函数会释放p指向的内存块，但是不会改变p本身​。

    如:
    free(p);
    strcpy(p,"abc"); //wrong 

    试图访问或修改释放掉的内存块会导致未定义的行为。试图修改释放掉的内存块可能会引起程序崩溃等损失惨重的后果。

    悬空指针是很难发现的，因为几个指针可能指向相同的内存块。在释放内存块后，全部的指针都悬空了。

  */
  
}













